
#include <BTextEdit>
#include <BStyle>
#include <BXmlNode>

#include "HtmlFormat.h"

inline bool IS_Space(char c)
{
	return c == ' '
		|| c == '\t'
		|| c == '\n'
		|| c == '\r'
		|| c == '\v';
}

HtmlFormat::HtmlFormat(const BString& name) : BTextFormat(name)
{
	_textEdit = 0;
	_colorMap["node"].set(0, 0, 100);
	_colorMap["attrib"].set(0, 120, 0);
	_colorMap["value"].set(100, 0, 100);
	_colorMap["comment"].set(10, 128, 10);
	connect(this, Signal_AssetChanged, &HtmlFormat::slotAssetChanged);
}
HtmlFormat::~HtmlFormat()
{

}

void HtmlFormat::styled(const BStyle* style)
{

}
void HtmlFormat::changed(const BString& xmltext)
{
	node = 0;
	topNodes.clear();
	BString name;
	BString text;
	int begin = 0;
	char code = 0;
	int pos = 0;
	while (pos < xmltext.size())
	{
		code = xmltext[pos++];
		if (IS_Space(code))
			begin = pos;
		if (text.empty() && code != '<')
			continue;
		text.append(code);
		if (code == '-' && text.endWith("<!--"))
		{
			node = new BXmlNode("<!--");
			insertItem(begin, pos, "comment");
			readComment(node, xmltext, pos);
			topNodes.append(node);
			continue;
		}
		if (code == '>')
		{
			if (text.beginWith("<!"))
			{
				text.reset();
				begin = pos;
				continue;
			}
			if (text.beginWith("<?") && text.endWith("?>"))
			{
				int name_end = readTagName(text, name);
				node = new BXmlNode(name);
				insertItem(begin, pos, "node");

				text.remove(0, name_end);
				text.remove(text.size() - 2);
				begin = xmltext.find(' ', begin, pos);
				readAttribs(begin, pos);
				name.lower();
				if (name == "<?xml")
				{
					version = node->attrib("version");
					encoding = node->attrib("encoding");
				}
				topNodes.append(node);
				text.reset();
				begin = pos;
				continue;
			}
			if (text.first() == '<')
			{
				int name_end = readTagName(text, name);
				node = new BXmlNode(name);
				insertItem(begin, pos, "node");
				if (text.endWith("/>"))
				{
					text.remove(0, name_end);
					text.remove(text.size() - 2);
					begin = xmltext.find(' ', begin, pos);
					readAttribs(begin, pos);
				}
				else
				{
					begin = xmltext.find(' ', begin, pos);
					readAttribs(begin, pos);
					readNode(node, xmltext, pos);
				}
				topNodes.append(node);
				text.reset();
				begin = pos;
				continue;
			}
			break;
		}
	}
	if (topNodes.empty())
	{
		node = 0;
		return;
	}
	node = topNodes.first();
	for (int i = 0; i < topNodes.size(); i++)
	{
		BXmlNode* node = topNodes[i];
	}
}

const BFont* HtmlFormat::font(int pos) const
{
	auto it = _itemMap <= (pos);
	if (it.valid())
	{
		if (pos < it->range.max())
			return it->font;
	}
	return 0;
}
const BColor* HtmlFormat::back(int pos) const
{
	return 0;
}
const BColor* HtmlFormat::color(int pos) const
{
	auto it = _itemMap <= (pos);
	if (it.valid())
	{
		if (pos < it->range.max())
			return &it->color;
	}
	return 0;
}

void HtmlFormat::insertItem(int pos, int end, const char* type)
{
	auto color = _colorMap.find(type);
	if (color.empty())
		return;
	auto it = _itemMap.insert(pos);
	it->range.set(pos, end);
	it->color = *color;
}

int HtmlFormat::readTagName(const BString& text, BString& tagName)
{
	int begin = 1;
	if (text[1] == '/')
		begin = 2;
	int end = begin;
	while (end < text.size())
	{
		if (IS_Space(text[end]))
			break;
		if (text[end] == '/')
			break;
		if (text[end] == '>')
			break;
		end++;
	}
	tagName.reset(text, begin, end);
	return end;
}
void HtmlFormat::readAttribs(int begin, int end)
{
	const BString& text = _textEdit->text();
	BString label;
	BString value;
	int pos = begin;
	while (pos < end)
	{
		char c = text[pos];
		if (IS_Space(c))
		{
			begin = ++pos;
			continue;
		}
		if (c == '=')
		{
			text.substr(label, begin, pos);
			insertItem(begin, pos, "attrib");
		}
		if (c == '\"')
		{
			begin = pos;
			pos = text.find('\"', pos + 1, end);
			text.substr(value, begin + 1, pos);
			insertItem(begin, pos, "value");
			begin = pos + 1;
		}
		pos++;
	}
}

bool HtmlFormat::readComment(BXmlNode* node, const BString& sontext, int& pos)
{
	if (node->name() == "<!--")
	{
		int begin = pos;
		char linebreak = 0;
		int end = sontext.find("-->", pos);
		while (pos < end)
		{
			char code = sontext[pos++];
			if (code == '\n' && pos > linePos)
			{
				lineNumber++;
				linePos = pos;
				continue;
			}
		}
		BString text;
		sontext.substr(text, begin, pos - 2);
		node->setText(text);
		return true;
	}
	return false;
}
bool HtmlFormat::readNode(BXmlNode* node, const BString& xmltext, int& pos)
{
	BString text;
	bool quot_begin = false;
	int tag_head = -1;
	int tag_tail = -1;
	int begin = pos;
	char code = 0;
	while (pos < xmltext.size())
	{
		code = xmltext[pos];
		if (IS_Space(code) && text.empty())
		{
			begin = ++pos;
			continue;
		}
		text.append(code);
		pos++;
		if (code == '\n')
		{
			continue;
		}
		if (code == '-' && text.endWith("<!--"))
		{
			BXmlNode* child = new BXmlNode("<!--");
			insertItem(begin, pos, "node");
			readComment(child, xmltext, pos);
			continue;
		}
		if (quot_begin)
		{
			if (code == '"')
			{
				quot_begin = false;
			}
			continue;
		}
		if (code == '"')
		{
			quot_begin = true;
			continue;
		}
		if (code == '<')
		{
			tag_head = pos - 1;
		}
		if (code == '>')
		{
			tag_tail = pos;
		}
		if (tag_tail > tag_head)
		{
			BString tagText;
			xmltext.substr(tagText, tag_head, tag_tail);

			BString tagName;
			int name_end = readTagName(tagText, tagName);

			if (tagText.beginWith("</"))
			{
				insertItem(tag_head, tag_tail, "node");
				const BString& name = node->name();
				if (tagName != name)
					return false;
				break;
			}
			if (tagText.endWith("/>"))
			{
				BXmlNode* child = new BXmlNode(tagName, node);
				insertItem(begin, pos, "node");

				tagText -= "/>";
				tagText.remove(0, name_end);
				begin = xmltext.find(' ', begin, pos);
				readAttribs(begin, pos);

				text.remove(tag_head, tag_tail);
				text.simplify();
				if (text.size() && text != ' ')
				{
					if (node->childCount() == 0)
						node->setText(text);
					else
						node->addChild("text", text);
					text.reset();
					begin = pos;
				}
			}
			else
			{
				BXmlNodeHolder child = new BXmlNode(tagName);
				insertItem(begin, pos, "node");
				tagText -= '>';
				tagText.remove(0, name_end);
				begin = xmltext.find(' ', begin, pos);
				readAttribs(begin, pos);
				begin = pos;
				int old_pos = pos;
				if (readNode(child, xmltext, pos))
				{
					text.remove(tag_head, tag_tail);
					text.simplify();
					if (text.size() && text != ' ')
					{
						if (node->childCount() == 0)
							node->setText(text);
						else
							node->addChild("text", text);
					}
					text.reset();
					node->addChild(child);
				}
				else
				{
					pos = old_pos;
				}
			}
			tag_head = -1;
			tag_tail = -1;
		}
	}

	return true;
}

void HtmlFormat::slotAssetChanged()
{
	_textEdit = this->asset();
}
